import csv
from typing import List
import x_com_lib
from code_holder import CodeHolder
from common import market_to_str
from converter import copy_min_data, subscribe_rt_data_to_topic
from rt_data.rt_data_holder import RTDataHolder
from x_com import xport_core
from x_com import source_api
from x_com.import_api import realtime_kdata_service_0_0_1
logger = xport_core.getLogger()


class RtdataSubscriberService:
    # 前面的初始化等方法保持不变
    async def on_init(self):
        self.data_path = x_com_lib.config.get_str("data-path")
        if self.data_path is None or self.data_path.strip() == "":
            raise Exception("data-path 不能为空！")
        # self.data_path = "C:/project"
        # 创建实时行情持有对象
        self.rt_data_holder = RTDataHolder()
        self.code_holder = CodeHolder()
        self.code_holder.load(self.data_path)

        logger.info("on_init...")

    async def on_finalize(self):
        logger.info("on_finalize...")

    # SubscribeRtData等其他已有方法保持不变

    async def subscribe_rt_data(self, ctx: xport_core.Context, param: source_api.SubscribeRTDataRequest):
        subscibe_info = subscribe_rt_data_to_topic(param)
        x_com_lib.stream.dxc_subscribe_topic(subscibe_info)
        subscribe_param = realtime_kdata_service_0_0_1.SubscribeRTDataRequest()
        subscribe_param.market = param.market
        subscribe_param.code = param.code
        await realtime_kdata_service_0_0_1.subscribe_rt_data(subscribe_param)

    async def cancel_subscribe_rt_data(self, ctx: xport_core.Context, param: source_api.SubscribeRTDataRequest):
        subscibe_info = subscribe_rt_data_to_topic(param)
        x_com_lib.stream.dxc_cancel_subscribe_topic(subscibe_info)
        subscribe_param = realtime_kdata_service_0_0_1.SubscribeRTDataRequest()
        subscribe_param.market = param.market
        subscribe_param.code = param.code
        await realtime_kdata_service_0_0_1.cancel_subscribe_rt_data(subscribe_param)

    async def clear_min_data(self, ctx: xport_core.Context, param: source_api.RTDataRequest):
        market_str = market_to_str(param.market)
        order_book_id = f"{market_str}.{param.code}"
        self.rt_data_holder.clear_min_data(order_book_id)

    def calc_average(self, min_datas: List[source_api.MinOrDayData]):
        turn_over = 0.0
        volume = 0.0
        #  计算的是分钟的均价线
        for min_data in min_datas:
            volume += min_data.volume
            turn_over += min_data.volume * min_data.close
            if volume!= 0:
                min_data.average = turn_over / volume

    async def get_rt_min_data(self, ctx: xport_core.Context, param: source_api.RTDataRequest) -> source_api.MinOrDayDataList:
        # 判断是否已经订阅，没有订阅的话，调用接口订阅
        market_str = market_to_str(param.market)
        order_book_id = f"{market_str}.{param.code}"
        min_datas = self.rt_data_holder.get_min_data(order_book_id)
        min_data_list = source_api.MinOrDayDataList()
        min_data_list.datas = min_datas
        # 当数据不为空，并且需要填充时，填充
        if not param.filled_up:
            # 计算的均价线
            self.calc_average(min_data_list.datas)
            return min_data_list

        # 数据为0 时，默认时间长度
        if len(min_data_list.datas) == 0:
            # 默认使用 20241105 因为录制的行情在 20241105 开始，真实情况，会使用当前交易日
            cur_day = 20241105
        else:
            # 根据实际行情，计算出真实的交易日
            cur_day = min_data_list.datas[0].datetime / 1_00_00_00_000
        # 获取交易时间点
        time_scope = self.code_holder.get_min_time_scope(order_book_id, int(cur_day), param.fre_num)
        # 如果交易时间点，为0 直接返回，这种情况基本不会发生，但是为了程序的严谨性，会增加判断
        if len(time_scope) == 0:
            return min_data_list

        cur_min_data_index = 0
        # 填充好的分钟数据
        fillup_min_datas = []
        # 对齐行情
        i = 0
        data_len = len(min_data_list.datas)
        # 1 分钟频率
        if param.fre_num == 1:
            # 判断当前是否有数据, 没有的话,以时间轴第一个交易时间为当前时间
            if len(min_data_list.datas) == 0:
                cur_min_data = source_api.MinOrDayData()
                cur_min_data.datetime = time_scope[0]
            else:
                # 如果已经有时间, 则获取第一根 bar 的时间
                cur_min_data = min_data_list.datas[cur_min_data_index]
            while i < len(time_scope):
                cur_datetime = time_scope[i]
                #
                if cur_min_data.datetime < cur_datetime and cur_min_data_index < data_len:
                    cur_min_data_index += 1
                    if cur_min_data_index < data_len:
                        cur_min_data = min_data_list.datas[cur_min_data_index]
                    continue
                # 等于当前的时间
                if cur_min_data.datetime == cur_datetime:
                    fillup_min_datas.append(cur_min_data)
                    # 分钟线移动到下一根
                    if cur_min_data_index < len(min_data_list.datas) - 1:
                        cur_min_data_index += 1
                        cur_min_data = min_data_list.datas[cur_min_data_index]
                # 大于当前时间，补齐
                elif cur_min_data.datetime > cur_datetime:
                    # 拿当前的时间进行补齐
                    new_min_data = copy_min_data(cur_min_data, cur_datetime)
                    fillup_min_datas.append(new_min_data)
                else:
                    # 后面的行情还没刷新，全部为0
                    new_min_data = source_api.MinOrDayData()
                    new_min_data.datetime = cur_datetime
                    fillup_min_datas.append(new_min_data)
                i += 1
            min_data_list.datas = fillup_min_datas
        else:
            # 填充好的分钟数据
            fillup_min_datas = []
            cur_min_data = source_api.MinOrDayData()
            cur_min_data.datetime = time_scope[0]
            fillup_min_datas.append(cur_min_data)
            if len(min_data_list.datas)!= 0:
                first_bar_data = min_data_list.datas[0]
                #  只要价格,即可用于判断最高价最低价
                cur_min_data.open = first_bar_data.open
                cur_min_data.high = first_bar_data.high
                cur_min_data.low = first_bar_data.low
                cur_min_data.close = first_bar_data.close
                cur_min_data.settlement = 0
                cur_min_data.volume = 0
                cur_min_data.total_turnover = 0
                cur_min_data.open_interest = 0
            # 第一根数据的时间
            while i < len(time_scope):
                # 当前的时间点
                cur_datetime = time_scope[i]
                #  这一步, 当前的 数据没有刷新到这个时间点,数据全部填充为 0 即可
                if cur_min_data_index >= len(min_data_list.datas):
                    # 已经添加过了,同一个时间点不添加
                    if cur_min_data.datetime!= cur_datetime:
                        cur_min_data = source_api.MinOrDayData()
                        cur_min_data.datetime = cur_datetime
                        fillup_min_datas.append(cur_min_data)
                    i += 1
                    continue
                #
                # 如果当前的数据, 小于当前的时间点, 要合并到当前的时间里面
                if min_data_list.datas[cur_min_data_index].datetime < cur_datetime:
                    # 合并数据
                    if cur_min_data.high < min_data_list.datas[cur_min_data_index].high:
                        cur_min_data.high = min_data_list.datas[cur_min_data_index].high
                    if cur_min_data.low > min_data_list.datas[cur_min_data_index].low:
                        cur_min_data.low = min_data_list.datas[cur_min_data_index].low
                    cur_min_data.close = min_data_list.datas[cur_min_data_index].close
                    # 把这个时间段内的数据,都相加
                    cur_min_data.settlement += min_data_list.datas[cur_min_data_index].settlement
                    cur_min_data.volume += min_data_list.datas[cur_min_data_index].volume
                    cur_min_data.total_turnover += min_data_list.datas[cur_min_data_index].total_turnover
                    cur_min_data.open_interest += min_data_list.datas[cur_min_data_index].open_interest
                    #  移动到下一根数据
                    cur_min_data_index += 1
                    continue
                # 把数据存到数据
                # 切换到下一个频率的数据
                # 到结尾了,不添加
                i += 1
                if i >= len(time_scope):
                    continue
                cur_min_data = source_api.MinOrDayData()
                #  添加数据
                fillup_min_datas.append(cur_min_data)
                # 切换到下一个时间点
                cur_min_data.datetime = time_scope[i]
                cur_min_data.open = min_data_list.datas[cur_min_data_index].open
                cur_min_data.high = min_data_list.datas[cur_min_data_index].high
                cur_min_data.low = min_data_list.datas[cur_min_data_index].low
                cur_min_data.close = min_data_list.datas[cur_min_data_index].close
                cur_min_data.settlement = min_data_list.datas[cur_min_data_index].settlement
                cur_min_data.volume = min_data_list.datas[cur_min_data_index].volume
                cur_min_data.total_turnover = min_data_list.datas[cur_min_data_index].total_turnover
                cur_min_data.open_interest = min_data_list.datas[cur_min_data_index].open_interest
                cur_min_data_index += 1
                # 这里判断数据已经到最后一根了
                if cur_min_data_index >= len(min_data_list.datas):
                    fillup_min_datas.append(cur_min_data)
            min_data_list.datas = fillup_min_datas
        # 计算的均价线
        self.calc_average(min_data_list.datas)
        return min_data_list

    async def search_code(self, ctx: xport_core.Context, param: source_api.SearchCodeRequest) -> source_api.CodeInfoList:
        # 搜索代码
        code_list = source_api.CodeInfoList()
        for code_info in self.code_holder.code_dict.values():
            # 取前 50 即可
            if len(code_list.datas) >= 50:
                break
            if code_info.code.startswith(param.keyword) or code_info.name.startswith(param.keyword) or code_info.short_en.startswith(param.keyword):
                code_list.datas.append(code_info)
                continue

            if code_info.trading_code is not None and code_info.trading_code.startswith(param.keyword):
                code_list.datas.append(code_info)

        return code_list

    # 新增的方法，用于将分钟数据持久化为CSV文件
    def persist_min_data_to_csv(self, min_data_list: source_api.MinOrDayDataList, file_path: str):
        """
        将分钟数据列表持久化为CSV文件。

        参数:
        min_data_list: 包含分钟数据的列表，类型为source_api.MinOrDayDataList，其内部包含source_api.MinOrDayData对象。
        file_path: 要保存的CSV文件路径。
        """
        header = ["datetime", "open", "high", "low", "close", "volume", "total_turnover", "open_interest", "settlement"]
        with open(file_path, 'w', newline='') as csvfile:
            writer = csv.writer(csvfile)
            writer.writerow(header)
            for min_data in min_data_list.datas:
                row = [
                    min_data.datetime,
                    min_data.open,
                    min_data.high,
                    min_data.low,
                    min_data.close,
                    min_data.volume,
                    min_data.total_turnover,
                    min_data.open_interest,
                    min_data.settlement
                ]
                writer.writerow(row)